﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Net;
using System.Threading;
using System.Reflection;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Security.Principal;
using WcfContrib.Client.Description;
using WcfContrib.Client.Behaviors;
using WcfContrib.Internal;
using WcfContrib.Resources;
using WcfContrib.Helpers;
using WcfContrib.Extensions.Behaviors;
using WcfContrib.Configuration;

namespace WcfContrib.Client
{
    /// <summary>
    /// Represents a client channel communication manager for communicating with services
    /// </summary>
    /// <typeparam name="TInstance">The type of the client instance.</typeparam>
    /// <typeparam name="TContract">The type of the service contract.</typeparam>
    public class ClientChannel<TInstance, TContract> : ClientChannel<TContract>
        where TInstance : ClientChannel<TContract>, new()
        where TContract : class
    {
        #region Fields
        static ChannelManageOptions? globalOptions;
        #endregion

        #region Instancing
        private static TInstance instance;
        /// <summary>
        /// Gets the client singleton instance.
        /// Exposed with 'SingleCall' management option.
        /// </summary>
        /// <value>The client singleton instance.</value>
        public new static TInstance Instance
        {
            get
            {
                return instance;
            }
        }

        static ClientChannel()
        {
            LoadRuntime();

            instance = new TInstance();
            instance.ManageOptions = globalOptions.HasValue ? globalOptions.Value : ChannelManageOptions.SingleCall;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TInstance, TContract&gt;"/> class.
        /// </summary>
        public ClientChannel()
            : base(false)
        {
            OnInitialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TInstance, TContract&gt;"/> class.
        /// </summary>
        /// <param name="manageOptions">The communication channel management options.</param>
        public ClientChannel(ChannelManageOptions manageOptions)
            : base(manageOptions, false)
        {
            OnInitialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TInstance, TContract&gt;"/> class.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        public ClientChannel(string endpointName)
            : base(endpointName, false)
        {
            OnInitialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TInstance, TContract&gt;"/> class.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <param name="manageOptions">The communication channel management options.</param>
        public ClientChannel(string endpointName, ChannelManageOptions manageOptions)
            : base(endpointName, manageOptions, false)
        {
            OnInitialize();
        }

        /// <summary>
        /// Sets the channel management options for the singleton instance
        /// </summary>
        /// <param name="manageOptions">The channel management options</param>
        public static void SetGlobalInstanceOptions(ChannelManageOptions manageOptions)
        {
            globalOptions = manageOptions;

            if (instance != null)
            {
                instance.ManageOptions = globalOptions.Value;
            }
        }

        void OnInitialize()
        {
            SetDescription(contractDescription.CloneCore(), typeEndpointBehaviors);

            InitializeRuntime();
        }
        #endregion

        #region Load Runtime
        static bool runtimeLoaded;
        static ClientContractDescription contractDescription;
        static List<IClientEndpointBehavior> typeEndpointBehaviors;

        /// <summary>
        /// Load the contract runtime information
        /// </summary>
        public static new void LoadRuntime()
        {
            if (!runtimeLoaded)
            {
                lock (typeof(ClientChannel<TInstance, TContract>))
                {
                    if (!runtimeLoaded)
                    {
                        ContractRuntimeDescription runtimeDescription = GetRuntimeDescription(typeof(TContract));
                        contractDescription = runtimeDescription.ContractDescription.CloneCore();

                        Type managerType = typeof(TInstance);
                        var methods = managerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);

                        foreach (var method in methods)
                        {
                            string asyncKey = GetMethodKey(method, true, false);
                            string asyncManipulateKey = GetMethodKey(method, true, true);
                            string key = GetMethodKey(method, false, false);

                            ContractMethod cmethod = runtimeDescription.ContractMethods
                                .Where(c => c.Key == asyncKey || c.Key == key || c.Key == asyncManipulateKey)
                                .Select(c => c.Value).FirstOrDefault();

                            if (cmethod != null)
                            {
                                contractDescription.GetOperationDescription(cmethod.OperationName).LoadMethod(method);
                            }
                        }

                        contractDescription.LoadContract(managerType);

                        typeEndpointBehaviors = managerType.GetCustomAttributes(true).Where(o => o is IClientEndpointBehavior).OfType<IClientEndpointBehavior>().ToList();
                        typeEndpointBehaviors.AddRange(runtimeDescription.EndpointBehaviors);

                        runtimeLoaded = true;
                    }
                }
            }
        }

        /// <summary>
        /// Initializes the client channel runtime.
        /// A proper place for dealing with the client description.
        /// </summary>
        protected virtual void InitializeRuntime()
        {
        }
        #endregion
    }

    /// <summary>
    /// Represents a client channel communication manager for communicating with services
    /// </summary>
    /// <typeparam name="TContract">The type of the service contract.</typeparam>
    public class ClientChannel<TContract> : ClientChannel
        where TContract : class
    {
        #region Fields
        TContract proxyChannel;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the proxy channel.
        /// </summary>
        /// <value>The proxy channel.</value>
        public TContract Channel
        {
            get
            {
                return proxyChannel;
            }
        }

        private static string globalEndpointName;
        /// <summary>
        /// Gets or sets the global name of the endpoint in the configuration.
        /// </summary>
        /// <value>The global name of the endpoint.</value>
        public static string GlobalEndpointName
        {
            get { return globalEndpointName; }
            set { globalEndpointName = value; }
        }
        #endregion

        #region Instancing
        private static ClientChannel<TContract> instance;
        /// <summary>
        /// Gets the client singleton instance.
        /// Exposed with 'SingleCall' management option.
        /// </summary>
        /// <value>The client singleton instance.</value>
        public static ClientChannel<TContract> Instance
        {
            get
            {
                return instance;
            }
        }

        static ClientChannel()
        {
            LoadRuntime();

            instance = new ClientChannel<TContract>();
            instance.ManageOptions = ChannelManageOptions.SingleCall;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TContract&gt;"/> class.
        /// </summary>
        public ClientChannel()
            : this(true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TContract&gt;"/> class.
        /// </summary>
        /// <param name="manageOptions">The communication channel management options.</param>
        public ClientChannel(ChannelManageOptions manageOptions)
            : this(manageOptions, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TContract&gt;"/> class.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        public ClientChannel(string endpointName)
            : this(endpointName, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TContract&gt;"/> class.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <param name="manageOptions">The communication channel management options.</param>
        public ClientChannel(string endpointName, ChannelManageOptions manageOptions)
            : this(endpointName, manageOptions, true)
        {
        }

        internal ClientChannel(bool setDescription)
            : base(typeof(TContract), setDescription)
        {
            OnInitialize();
        }

        internal ClientChannel(ChannelManageOptions manageOptions, bool setDescription)
            : base(typeof(TContract), manageOptions, setDescription)
        {
            OnInitialize();
        }

        internal ClientChannel(string endpointName, bool setDescription)
            : base(typeof(TContract), endpointName, setDescription)
        {
            OnInitialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel&lt;TContract&gt;"/> class.
        /// </summary>
        /// <param name="endpointName">Name of the endpoint.</param>
        /// <param name="manageOptions">The manage options.</param>
        /// <param name="setDescription">if set to <c>true</c> [set description].</param>
        internal ClientChannel(string endpointName, ChannelManageOptions manageOptions, bool setDescription)
            : base(typeof(TContract), endpointName, manageOptions, setDescription)
        {
            OnInitialize();
        }

        void OnInitialize()
        {
            proxyChannel = (TContract)base.Proxy.GetTransparentProxy();
        }
        #endregion

        #region Load Runtime
        /// <summary>
        /// Load the contract runtime information
        /// </summary>
        public static void LoadRuntime()
        {
            LoadRuntime(typeof(TContract));
        }
        #endregion

        #region Channel Handling
        /// <summary>
        /// Creates the communication channel.
        /// </summary>
        /// <param name="factory">The channel factory.</param>
        /// <returns></returns>
        protected override object CreateChannelCore(ChannelFactory factory)
        {
            return ((ChannelFactory<TContract>)factory).CreateChannel();
        }

        /// <summary>
        /// Creates the channel factory.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <returns></returns>
        protected override ChannelFactory CreateChannelFactoryCore(string endpointName)
        {
            ChannelFactory<TContract> factory = new ChannelFactory<TContract>(endpointName);

            factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;

            return factory;
        }

        internal override string GetDefaultEndpointName()
        {
            return GlobalEndpointName;
        }
        #endregion
    }

    /// <summary>
    /// Represents a client channel communication manager for communicating with services
    /// </summary>
    public abstract class ClientChannel : IExtensibleObject<ClientChannel>, IDisposable
    {
        #region Settings
        internal const ChannelManageOptions DefaultManageOptions = ChannelManageOptions.SingleCall;
        internal const bool EndChannelUsageUsingAbort = false;
        internal const bool DisposeChannelUsingAbort = false;
        internal const string AsyncAggregate = "Async";
        internal const int ChannelWriterLockTimeoutInMs = 5 * 1000;
        internal const int BeginCloseTimeoutInSecondsDefault = 10;
        const string DefaultEndpointName = "*";
        internal const string MethodBeginInitial = "Begin";
        internal const string MethodEndInitial = "End";
        #endregion

        #region ClientProxy
        /// <summary>
        /// Represents the client proxy used to delegate calls
        /// </summary>
        protected internal class ClientProxy : RealProxy
        {
            internal ClientChannel Client { get; private set; }

            internal ClientProxy(ClientChannel client, Type channelType)
                : base(channelType)
            {
                Client = client;
            }

            /// <summary>
            /// Invokes the specified message.
            /// </summary>
            /// <param name="message">The message.</param>
            /// <returns></returns>
            public override IMessage Invoke(IMessage message)
            {
                IMethodCallMessage methodCall = message as IMethodCallMessage;
                if (methodCall == null)
                {
                    throw new ArgumentException("Expected an IMethodCallMessage.", "message");
                }
                else
                {
                    ReturnMessage returnMessage = null;
                    while (returnMessage == null)
                    {
                        try
                        {
                            object[] args = methodCall.Args;

                            object result = Client.InvokeChannel(methodCall.MethodBase, args);

                            int outArgsCount;
                            object[] outArgs = GetOutArguments(methodCall, args, out outArgsCount);

                            returnMessage = new ReturnMessage(result, outArgs,
                                outArgsCount, methodCall.LogicalCallContext, methodCall);
                        }
                        catch (Exception ex)
                        {
                            returnMessage = new ReturnMessage(ex, methodCall);
                        }
                    }

                    return returnMessage;
                }
            }

            private object[] GetOutArguments(IMethodCallMessage methodCall, object[] args, out int count)
            {
                //TODO: optimize it using some sort of caching.
                ParameterInfo[] paramInfos = methodCall.MethodBase.GetParameters();
                int countTemp = 0;

                List<object> outParameters = new List<object>();
                for (int i = 0; i < args.Length; ++i)
                {
                    ParameterInfo parameterInfo = paramInfos[i];
                    if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef)
                    {
                        ++countTemp;
                        outParameters.Add(args[i]);
                    }
                    else
                    {
                        outParameters.Add(null);
                    }
                }

                count = countTemp;

                return outParameters.ToArray();
            }
        }
        #endregion

        #region Fields
        Type clientChannelType;
        object channel;
        bool hasAddress;
        bool addressExternalSet;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the client channel endpoint description.
        /// </summary>
        /// <value>The client channel endpoint description.</value>
        public ClientEndpointDescription Description { get; internal set; }

        private Type contractType;
        /// <summary>
        /// Gets the type of the service contract.
        /// </summary>
        /// <value>The type of the service contract.</value>
        public Type ContractType
        {
            get { return contractType; }
            private set { contractType = value; }
        }

        private ClientProxy proxy;
        /// <summary>
        /// Gets the inner proxy.
        /// </summary>
        /// <value>The proxy.</value>
        internal ClientProxy Proxy
        {
            get { return proxy; }
            set { proxy = value; }
        }

        private string address;
        /// <summary>
        /// Gets or sets the endpoint address.
        /// Setting it is supported at any stage on the contrary of getting it (will be populated only after the first invocation)
        /// </summary>
        /// <value>The endpoint address.</value>
        public string Address
        {
            get { return address; }
            set
            {
                address = value;

                addressExternalSet = true;
                hasAddress = !string.IsNullOrEmpty(address);
            }
        }

        private bool cacheChannelFactory = true;
        /// <summary>
        /// Gets or sets a value indicating whether the client channel should cache the channel factory.
        /// Defaults to <c>true</c>
        /// </summary>
        /// <value><c>true</c> if the client channel should cache the channel factory; otherwise, <c>false</c>.</value>
        public bool CacheChannelFactory
        {
            get { return cacheChannelFactory; }
            set { cacheChannelFactory = value; }
        }

        private ChannelManageOptions manageOptions;
        /// <summary>
        /// Gets the communication management options.
        /// </summary>
        /// <value>The communication management options.</value>
        public ChannelManageOptions ManageOptions
        {
            get { return manageOptions; }
            internal set { manageOptions = value; }
        }

        private NetworkCredential credentials = null;
        /// <summary>
        /// Gets or sets the client network credentials.
        /// </summary>
        /// <value>The client network credentials.</value>
        public NetworkCredential Credentials
        {
            get { return credentials; }
            set
            {
                credentials = value;

                if (cachedFactory != null)
                {
                    ResetChannels();
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to ignore the endpoint aggregation.
        /// </summary>
        /// <value>
        /// 	<c>true</c> to ignore the endpoint aggregation; otherwise, <c>false</c>.
        /// </value>
        public bool IgnoreEndpointAggregation { get; set; }

        private string endpointName;
        /// <summary>
        /// Gets or sets the name of the endpoint in the configuration.
        /// </summary>
        /// <value>The name of the endpoint.</value>
        public string EndpointName
        {
            get
            {
                if (string.IsNullOrEmpty(endpointName))
                {
                    return GetDefaultEndpointName();
                }

                return endpointName;
            }
            set { endpointName = value; }
        }

        private bool throwInlineFaultExceptions = ClientModel.ThrowInlineFaultExceptions;
        /// <summary>
        /// Gets or sets a value indicating whether to throw generic FaultException&lt;T&gt; inner known exception.
        /// Defaults to <c>false</c>
        /// </summary>
        /// <value>
        /// 	<c>true</c> if o throw generic FaultException&lt;T&gt; inner known exception; otherwise, <c>false</c>.
        /// </value>
        public bool ThrowInlineFaultExceptions
        {
            get { return throwInlineFaultExceptions; }
            set { throwInlineFaultExceptions = value; }
        }

        private int beginCloseTimeoutInSeconds = BeginCloseTimeoutInSecondsDefault;
        /// <summary>
        /// Gets or sets the asynchronous close timeout in seconds.
        /// </summary>
        /// <value>The begin close timeout in seconds.</value>
        public int BeginCloseTimeoutInSeconds
        {
            get { return beginCloseTimeoutInSeconds; }
            set { beginCloseTimeoutInSeconds = value; }
        }

        private bool applyBoosting;
        /// <summary>
        /// Gets or sets a value indicating whether to apply endpoint and binding boosting.
        /// </summary>
        /// <value><c>true</c> if to apply endpoint and binding boosting; otherwise, <c>false</c>.</value>
        public bool ApplyBoosting
        {
            get { return applyBoosting; }
            set { applyBoosting = value; }
        }
        #endregion

        #region Instancing
        internal ClientChannel(Type contractType, bool setDescription)
        {
            this.contractType = contractType;
            clientChannelType = GetType();

            proxy = new ClientProxy(this, contractType);

            extensions = new ExtensionCollection<ClientChannel>(this);

            this.manageOptions = ClientChannel.DefaultManageOptions;

            ClientModel.EndpointAggregationChanged += OnEndpointAggregationChanged;
            applyBoosting = WcfContribConfigurationSection.GetSection().Client.ApplyBoosting;

            if (setDescription)
            {
                ContractRuntimeDescription runtimeDescription = GetRuntimeDescription(contractType);

                SetDescription(runtimeDescription.ContractDescription.CloneCore(),
                    runtimeDescription.EndpointBehaviors);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel"/> class.
        /// </summary>
        /// <param name="contractType">The type of the service contract.</param>
        /// <param name="manageOptions">The communication channel management options.</param>
        /// <param name="setDescription">if set to <c>true</c> [set description].</param>
        public ClientChannel(Type contractType, ChannelManageOptions manageOptions, bool setDescription)
            : this(contractType, setDescription)
        {
            this.manageOptions = manageOptions;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel"/> class.
        /// </summary>
        /// <param name="contractType">The type of the service contract.</param>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <param name="setDescription">if set to <c>true</c> [set description].</param>
        public ClientChannel(Type contractType, string endpointName, bool setDescription)
            : this(contractType, setDescription)
        {
            EndpointName = endpointName;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientChannel"/> class.
        /// </summary>
        /// <param name="contractType">The type of the service contract.</param>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <param name="manageOptions">The communication channel management options.</param>
        /// <param name="setDescription">if set to <c>true</c> the description is set.</param>
        public ClientChannel(Type contractType, string endpointName, ChannelManageOptions manageOptions, bool setDescription)
            : this(contractType, endpointName, setDescription)
        {
            this.manageOptions = manageOptions;
        }

        /// <summary>
        /// Disposes of the client channel and the communication channel
        /// </summary>
        public void Dispose()
        {
            ClientModel.EndpointAggregationChanged -= OnEndpointAggregationChanged;

            DisposeChannel(true);
        }
        #endregion

        #region Load Runtime
        #region ContractMethod
        internal class ContractMethod
        {
            internal MethodBase Method { get; set; }
            internal MethodBase EndMethod { get; set; }

            internal bool AsyncPattern { get; set; }
            internal bool IsOneWay { get; set; }

            internal string OperationName { get; set; }
        }
        #endregion

        #region ContractRuntimeDescription
        internal class ContractRuntimeDescription
        {
            internal Type ContractType { get; private set; }

            internal ClientContractDescription ContractDescription { get; set; }
            internal Dictionary<string, ContractMethod> ContractMethods { get; private set; }
            internal Dictionary<MethodBase, string> ClientMethods { get; private set; }
            internal List<IClientEndpointBehavior> EndpointBehaviors { get; private set; }

            internal ContractRuntimeDescription(Type contractType)
            {
                ContractType = contractType;

                ContractMethods = new Dictionary<string, ContractMethod>();
                ClientMethods = new Dictionary<MethodBase, string>();
                ContractDescription = new ClientContractDescription(contractType);
                EndpointBehaviors = new List<IClientEndpointBehavior>();
            }
        }
        #endregion

        static Dictionary<Type, ContractRuntimeDescription> contractRuntimeStore = new Dictionary<Type, ContractRuntimeDescription>();

        internal static void LoadRuntime(Type contractType)
        {
            GetRuntimeDescription(contractType);
        }

        internal static ContractRuntimeDescription GetRuntimeDescription(Type contractType)
        {
            ContractRuntimeDescription runtimeDescription;
            if (!contractRuntimeStore.TryGetValue(contractType, out runtimeDescription))
            {
                ContractRuntimeDescription runtimeDescriptionInternal = GetRuntimeDescriptionCore(contractType);

                if (!contractRuntimeStore.TryGetValue(contractType, out runtimeDescription))
                {
                    lock (contractRuntimeStore)
                    {
                        if (!contractRuntimeStore.TryGetValue(contractType, out runtimeDescription))
                        {
                            contractRuntimeStore.Add(contractType, runtimeDescriptionInternal);

                            runtimeDescription = runtimeDescriptionInternal;
                        }
                    }
                }
            }

            return runtimeDescription;
        }

        static ContractRuntimeDescription GetRuntimeDescriptionCore(Type contractType)
        {
            ContractRuntimeDescription runtimeDescription = new ContractRuntimeDescription(contractType);

            Dictionary<string, ContractMethod> contractMethods = runtimeDescription.ContractMethods;
            Dictionary<MethodBase, string> clientMethods = runtimeDescription.ClientMethods;

            List<Type> typesToExplore = new List<Type> { contractType };
            typesToExplore.AddRange(contractType.GetInterfaces());

            var methods =
                from t in typesToExplore
                from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)
                orderby m.Name
                select m;

            foreach (var method in methods)
            {
                object[] operContracts = method.GetCustomAttributes(typeof(OperationContractAttribute), true);

                if (operContracts != null && operContracts.Length > 0)
                {
                    OperationContractAttribute oper = (OperationContractAttribute)operContracts[0];

                    ContractMethod contractMethod = new ContractMethod
                    {
                        IsOneWay = oper.IsOneWay,
                        AsyncPattern = oper.AsyncPattern,
                        Method = method,
                        OperationName = string.IsNullOrEmpty(oper.Name) ? method.Name : oper.Name
                    };

                    contractMethods.Add(GetMethodKey(method, method.GetParameters(), contractMethod.AsyncPattern), contractMethod);

                    ClientOperationDescription operDesc = new ClientOperationDescription();

                    operDesc.LoadOperation(oper, method);

                    runtimeDescription.ContractDescription.AddOperationDescription(operDesc);
                }
                else if (method.Name.StartsWith(MethodEndInitial))
                {
                    string beginMethodName = MethodBeginInitial + method.Name.Remove(0, 3);

                    contractMethods[beginMethodName].EndMethod = method;
                }
            }

            foreach (var type in typesToExplore)
            {
                runtimeDescription.ContractDescription.LoadContract(type);

                //load endpoint behaviors
                var endpointBehaviors = contractType.GetCustomAttributes(true).Where(o => o is IClientEndpointBehavior).OfType<IClientEndpointBehavior>();
                runtimeDescription.EndpointBehaviors.AddRange(endpointBehaviors);
            }

            runtimeDescription.ContractDescription.OnLoadComplete();

            return runtimeDescription;
        }

        static string GetMethodKey(MethodBase method, ParameterInfo[] methodParameters, bool asyncPattern)
        {
            string methodName = method.Name;

            if (asyncPattern)
            {
                return methodName;
            }

            if (asyncPattern || methodParameters.Length == 0)
            {
                return methodName;
            }
            else
            {
                string pHash = (from p in methodParameters
                                select p.ParameterType.FullName).Aggregate(
                           (p1, p2) => string.Format("{0}**{1}", p1, p2));

                return string.Format("{0}&&{1}", methodName, pHash);
            }
        }

        static ContractMethod GetContractMethod(Type contractType, MethodBase clientChannelMethod, bool asyncPattern, bool manipulateAsyncKey)
        {
            ContractRuntimeDescription runtimeDescription = GetRuntimeDescription(contractType);

            Dictionary<string, ContractMethod> contractMethods = runtimeDescription.ContractMethods;
            Dictionary<MethodBase, string> clientMethods = runtimeDescription.ClientMethods;

            string key;
            if (!clientMethods.TryGetValue(clientChannelMethod, out key))
            {
                lock (clientMethods)
                {
                    if (!clientMethods.TryGetValue(clientChannelMethod, out key))
                    {
                        key = GetMethodKey(clientChannelMethod, asyncPattern, manipulateAsyncKey);

                        clientMethods.Add(clientChannelMethod, key);
                    }
                }
            }

            ContractMethod result;
            if (!contractMethods.TryGetValue(key, out result))
            {
                throw new InvalidOperationException(ExceptionMessages.ClientChannel_ContractMethodMismatch);
            }

            return result;
        }

        internal static string GetMethodKey(MethodBase method, bool asyncPattern, bool manipulateAsyncKey)
        {
            string key = string.Empty;

            if (!asyncPattern)
            {
                key = GetMethodKey(method, method.GetParameters(), false);
            }
            else if (manipulateAsyncKey)
            {
                string endsWith = ClientChannel.AsyncAggregate;

                string methodName = method.Name;
                if (methodName.EndsWith(endsWith))
                {
                    methodName = methodName.Remove(methodName.Length - endsWith.Length);
                }

                key = "Begin" + methodName;
            }
            else
            {
                key = method.Name;
            }

            return key;
        }

        internal void SetDescription(ClientContractDescription contractDescription, List<IClientEndpointBehavior> endpointBehaviors)
        {
            Description = new ClientEndpointDescription(this, contractDescription);

            Description.Behaviors.AddRange(endpointBehaviors, false);
        }
        #endregion

        #region Channel Handling
        void HandleChannelAfterCall(object channel, ContractMethod method, bool hasException, bool abort, ChannelContextMode mode)
        {
            if (channel != null)
            {
                bool performAbort = false;
                bool performClose = false;
                bool performBeginClose = false;

                if (hasException && HasOption(ChannelManageOptions.RenewFaultedChannel))
                {
                    performAbort = true;
                }
                else if (HasOption(ChannelManageOptions.CloseAsyncAfterOneWay) && method.IsOneWay)
                {
                    performBeginClose = true;
                }
                else if (HasOption(ChannelManageOptions.ChannelPerCall)
                    || (method.AsyncPattern && HasOption(ChannelManageOptions.AsyncOwnChannel)))
                {
                    if (abort || hasException)
                    {
                        performAbort = true;
                    }
                    else
                    {
                        performClose = true;
                    }
                }

                if (performAbort)
                {
                    GetProvider().AbortChannel(channel, mode);
                }
                else if (performBeginClose)
                {
                    GetProvider().CloseChannelAsync(channel, mode, beginCloseTimeoutInSeconds);
                }
                else if (performClose)
                {
                    GetProvider().CloseChannel(channel, mode);
                }
            }
        }

        object GetChannel(NetworkCredential credentials, ContractMethod method, out ChannelContextMode mode)
        {
            object channel = null;
            bool isCreated = false;

            IChannelProvider provider = GetProvider();

            bool useOwnFactory = credentials != null && credentials != this.credentials;

            //check if a dedicated channel is required
            if (HasOption(ChannelManageOptions.ChannelPerCall)
                || (method.AsyncPattern && HasOption(ChannelManageOptions.AsyncOwnChannel))
                || (method.IsOneWay && HasOption(ChannelManageOptions.CloseAsyncAfterOneWay)))
            {
                //create a dedicated channel with the cached factory
                mode = ChannelContextMode.Dedicated;
                channel = provider.GetChannel(GetChannelFactory(), mode, out isCreated);
            }
            else if (useOwnFactory)
            {
                //create a dedicated channel with new factory
                mode = ChannelContextMode.Dedicated;
                channel = provider.GetChannel(CreateChannelFactory(credentials, true), mode, out isCreated);
            }
            else
            {
                mode = ChannelContextMode.Shared;
                channel = provider.GetChannel(GetChannelFactory(), mode, out isCreated);
            }

            if (channel == null)
            {
                throw new InvalidOperationException(ExceptionMessages.ClientChannel_NullChannel);
            }

            if (isCreated)
            {
                Description.ChannelHandlers.ForEach(b => b.ChannelCreated(this, channel as ICommunicationObject));
            }

            return channel;
        }

        IChannelProvider GetProvider()
        {
            IChannelProvider provider = Description.ChannelProvider;
            if (provider == null)
            {
                throw new InvalidOperationException(ExceptionMessages.ClientChannel_NullChannelProvider);
            }

            return provider;
        }

        private object CreateChannelInternal(ChannelFactory factory)
        {
            object channel = CreateChannelCore(factory);

            if (channel == null)
            {
                throw new InvalidOperationException(ExceptionMessages.ClientChannel_NullChannel);
            }

            Description.ChannelHandlers.ForEach(b => b.ChannelCreated(this, channel as ICommunicationObject));

            return channel;
        }

        internal object CreateChannelCoreInternal(ChannelFactory factory)
        {
            return CreateChannelCore(factory);
        }

        /// <summary>
        /// Creates the communication channel.
        /// </summary>
        /// <param name="factory">The channel factory.</param>
        /// <returns></returns>
        protected abstract object CreateChannelCore(ChannelFactory factory);

        void ResetChannels()
        {
            DisposeChannel(false);
        }

        /// <summary>
        /// Generates the endpoint address.
        /// </summary>
        /// <returns>The endpoint address</returns>
        public string GenerateAddress()
        {
            if (hasAddress)
            {
                return address;
            }
            else
            {
                using (ChannelFactory factory = CreateChannelFactory(null, false))
                {
                    return factory.Endpoint.Address.Uri.AbsoluteUri;
                }
            }
        }
        #endregion

        #region Channel Lifetime
        /// <summary>
        /// Closes the communication channel.
        /// </summary>
        public void Close()
        {
            GetProvider().CloseChannel(null, ChannelContextMode.Shared);
            //RunSyncReusableChannelAction(true, () => channel != null, true, () => CloseInternal());
        }

        /// <summary>
        /// Aborts the communication channel.
        /// </summary>
        public void Abort()
        {
            GetProvider().AbortChannel(null, ChannelContextMode.Shared);
            //RunSyncReusableChannelAction(true, () => channel != null, true, () => AbortInternal());
        }

        void DisposeChannel(bool isDisposed)
        {
            if (isDisposed)
            {
                using (GetProvider() as IDisposable) { };
            }
            else 
            {
                GetProvider().TerminateChannel(null, ChannelContextMode.Shared);

                if (cachedFactory != null)
                {
                    lock (factorySync)
                    {
                        if (cachedFactory != null)
                        {
                            cachedFactory = null;
                        }
                    }
                }
            }
        }
        #endregion

        #region Channel Factory Handling
        ChannelFactory cachedFactory = null;
        object factorySync = new object();

        private ChannelFactory GetChannelFactory()
        {
            return CreateChannelFactory(credentials, true);
        }

        internal virtual string GetDefaultEndpointName()
        {
            return null;
        }

        private ChannelFactory CreateChannelFactory(NetworkCredential credentials, bool cachedOption)
        {
            ChannelFactory factory = null;

            string endPoint = GetEndpointName();

            bool hasCred = credentials != null;
            if (cachedOption && cacheChannelFactory)
            {
                factory = GetCachedChannelFactory(endPoint, address,
                    addressExternalSet, credentials, hasCred);
            }
            else
            {
                factory = CreateChannelFactoryCore(endPoint, address,
                        addressExternalSet, credentials, hasCred);
            }

            if (!hasAddress)
            {
                Address = factory.Endpoint.Address.Uri.AbsoluteUri;
                addressExternalSet = false;
            }

            return factory;
        }

        private ChannelFactory GetCachedChannelFactory(string endpointName, string address, bool setAddress, NetworkCredential credentials, bool hasCreds)
        {
            lock (factorySync)
            {
                if (cachedFactory == null)
                {
                    cachedFactory = CreateChannelFactoryCore(endpointName, address,
                        setAddress, credentials, hasCreds);
                }

                return cachedFactory;
            }
        }

        private ChannelFactory CreateChannelFactoryCore(string endpointName, string address, bool setAddress, NetworkCredential credentials, bool hasCreds)
        {
            if (string.IsNullOrEmpty(endpointName))
            {
                endpointName = DefaultEndpointName;
            }

            ChannelFactory factory = CreateChannelFactoryCore(endpointName);

            if (factory == null)
            {
                throw new InvalidOperationException(ExceptionMessages.ClientChannel_NullFactory);
            }

            if (setAddress)
            {
                factory.Endpoint.Address = new EndpointAddress(address);
            }

            if (hasCreds)
            {
                factory.Credentials.Windows.ClientCredential = credentials;
            }

            if (applyBoosting)
            {
                Description.Behaviors.AddUnique(new BoostedEndpointBehaviorAttribute());
            }

            OnChannelFactoryCreated(factory);

            Description.ChannelHandlers.ForEach(b => b.ChannelFactoryCreated(this, factory));

            return factory;
        }

        /// <summary>
        /// Creates the channel factory.
        /// </summary>
        /// <param name="endpointName">The name of the endpoint in the configuration.</param>
        /// <returns></returns>
        protected abstract ChannelFactory CreateChannelFactoryCore(string endpointName);

        /// <summary>
        /// Called when the channel factory is created.
        /// </summary>
        /// <param name="factory">The channel factory.</param>
        protected virtual void OnChannelFactoryCreated(ChannelFactory factory)
        {
        }
        #endregion

        #region Event Invocations
        void OnEndpointAggregationChanged(object sender, EventArgs e)
        {
            if (!IgnoreEndpointAggregation && cachedFactory != null)
            {
                ResetChannels();
            }
        }
        #endregion

        #region Exception Handling
        InvokeErrorAction HandleInvokeException(Exception exception, ContractMethod method, bool throwInlineFaultExceptions, object[] parameters)
        {
            return HandleInvokeException(exception, method, method.Method, throwInlineFaultExceptions, parameters);
        }

        InvokeErrorAction HandleInvokeException(Exception exception, ContractMethod method, MethodBase actualMethod, bool throwInlineFaultExceptions, object[] parameters)
        {
            return HandleInvokeException(false, null, AsyncInvokeStage.Begin, exception, method, actualMethod, throwInlineFaultExceptions, parameters);
        }

        InvokeErrorAction HandleInvokeException(AsyncInvocationParams asyncParams, AsyncInvokeStage stage, Exception exception, ContractMethod method, bool throwInlineFaultExceptions, object[] parameters)
        {
            return HandleInvokeException(asyncParams, stage, exception, method, method.Method, throwInlineFaultExceptions, parameters);
        }

        InvokeErrorAction HandleInvokeException(AsyncInvocationParams asyncParams, AsyncInvokeStage stage, Exception exception, ContractMethod method, MethodBase actualMethod, bool throwInlineFaultExceptions, object[] parameters)
        {
            return HandleInvokeException(true, asyncParams, stage, exception, method, method.Method, throwInlineFaultExceptions, parameters);
        }

        InvokeErrorAction HandleInvokeException(bool isAsync, AsyncInvocationParams asyncParams, AsyncInvokeStage stage,
            Exception exception, ContractMethod method, MethodBase actualMethod,
            bool throwInlineFaultExceptions, object[] parameters)
        {
            InvokeErrorAction errorAction;
            Exception throwException = OnClientException(exception, method, actualMethod, throwInlineFaultExceptions, parameters, out errorAction);

            if (errorAction == InvokeErrorAction.Throw)
            {
                if (isAsync && asyncParams != null)
                {
                    if (asyncParams.Callback != null)
                    {
                        asyncParams.Callback.Invoke(new ChannelInvokeAsyncResult(null, throwException, stage, asyncParams.AsyncState));
                    }
                }
                else
                {
                    throw throwException;
                }
            }

            return errorAction;
        }

        Exception OnClientException(Exception exception, ContractMethod method, MethodBase actualMethod, bool throwInlineFaultExceptions, object[] parameters, out InvokeErrorAction errorAction)
        {
            Exception properException = ClientChannel.GetProperException(exception, throwInlineFaultExceptions);

            ChannelInvokeContext context = new ChannelInvokeContext(clientChannelType, contractType, actualMethod, parameters);
            Description.Contract.ErrorHandlers.ForEach(e =>
            {
                try
                {
                    e.HandleError(this, context, properException);
                    e.ProvideException(this, context, ref properException);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(string.Format(ExceptionMessages.ClientChannel_ExtensionCall, e.GetType().FullName), ex);
                }
            });

            OnException(properException, actualMethod, parameters, out errorAction);

            return properException;
        }

        void OnException(Exception exception, MethodBase method, object[] parameters, out InvokeErrorAction errorAction)
        {
            ChannelExceptionEventArgs args = new ChannelExceptionEventArgs(clientChannelType, contractType, method, exception, parameters);

            errorAction = OnInvokeException(args);
        }

        /// <summary>
        /// Occurs when invocation exceptions take place in the specific client channel.
        /// </summary>
        public event EventHandler<ChannelExceptionEventArgs> InvokeException;
        /// <summary>
        /// Handles invocation error.
        /// Raises the <see cref="E:InvokeException"/> event.
        /// </summary>
        /// <param name="args">The <see cref="WcfContrib.Client.ChannelExceptionEventArgs"/> exception context.</param>
        /// <returns></returns>
        protected virtual InvokeErrorAction OnInvokeException(ChannelExceptionEventArgs args)
        {
            var evt = InvokeException;
            if (evt != null)
            {
                evt(this, args);
            }

            ClientChannel.OnGlobalInvokeException(this, args);

            return InvokeErrorAction.Throw;
        }
        #endregion

        #region Extensions
        bool HandleBeforeInvocation(ContractMethod method, MethodBase actualMethod, object[] parameters, out object result, out object[] correlationStates, out ChannelInvokeContext context)
        {
            context = new ChannelInvokeContext(clientChannelType, contractType, actualMethod, parameters);

            return HandleBeforeInvocation(context, method, actualMethod, parameters, out result, out correlationStates);
        }

        bool HandleBeforeInvocation(ChannelInvokeContext context, ContractMethod method, MethodBase actualMethod, object[] parameters, out object result, out object[] correlationStates)
        {
            result = null;
            bool invokeChannel = true;

            context.Method = actualMethod;
            context.Parameters = parameters;

            ClientOperationDescription operDesc = Description.Contract.GetOperationDescription(method.OperationName);

            correlationStates = new object[operDesc.Inspectors.Count];

            object[] correlate = new object[operDesc.Inspectors.Count];
            int inspectorIndex = 0;
            foreach (var inspector in operDesc.Inspectors)
            {
                try
                {
                    correlate[inspectorIndex] = inspector.BeforeCall(this, context);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(string.Format(ExceptionMessages.ClientChannel_ExtensionCall, inspector.GetType().FullName), ex);
                }
                
                inspectorIndex++;
            }

            correlationStates = correlate;

            foreach (var invoker in operDesc.Invokers)
            {
                object invokerCorrelate = null;
                IClientOperationInspectorBehavior inspector = invoker as IClientOperationInspectorBehavior;
                if (inspector != null)
                {
                    int correlationIndex = operDesc.Inspectors.IndexOf(inspector);

                    if (correlationIndex >= 0)
                    {
                        invokerCorrelate = correlate[correlationIndex];
                    }
                }

                bool providedResult;
                try
                {
                    result = invoker.ProvideResult(this, context, invokerCorrelate, out providedResult);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(string.Format(ExceptionMessages.ClientChannel_ExtensionCall, invoker.GetType().FullName), ex);
                }

                if (providedResult)
                {
                    invokeChannel = false;
                    break;
                }
            }

            return invokeChannel;
        }

        void HandleAfterInvocation(ChannelInvokeContext context, ContractMethod method, MethodBase actualMethod, object[] parameters, object result, object[] correlationStates)
        {
            ClientOperationDescription operDesc = Description.Contract.GetOperationDescription(method.OperationName);

            if (context == null)
            {
                context = new ChannelInvokeContext(clientChannelType, contractType, actualMethod, parameters);
            }

            operDesc.Inspectors.ForEach(
                (index, i) => 
                {
                    try
                    {
                        i.AfterCall(this, context, result, correlationStates[index]);
                    }
                    catch (Exception ex)
                    {
                        throw new InvalidOperationException(
                            string.Format(ExceptionMessages.ClientChannel_ExtensionCall, i.GetType().FullName), ex);
                    }
                });
        }
        #endregion

        #region Helper Methods
        /// <summary>
        /// Gets the final name of the endpoint in the configuration.
        /// </summary>
        /// <returns></returns>
        public string GetEndpointName()
        {
            string name = EndpointName;

            if (!IgnoreEndpointAggregation)
            {
                name += ClientModel.EndpointAggregation;
            }

            return name;
        }

        bool HasOption(ChannelManageOptions option)
        {
            return ((manageOptions & option) == option);
        }
        #endregion

        #region Invocations
        /// <summary>
        /// Invokes the service operation.
        /// </summary>
        /// <param name="methodBase">The current method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        protected object InvokeChannel(MethodBase methodBase, params object[] parameters)
        {
            return InvokeChannel<object>(methodBase, parameters);
        }

        /// <summary>
        /// Invokes the service operation.
        /// </summary>
        /// <typeparam name="T">The type of the result</typeparam>
        /// <param name="methodBase">The current method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        protected T InvokeChannel<T>(MethodBase methodBase, params object[] parameters)
        {
            return InvokeChannel<T>(null, methodBase, parameters);
        }

        /// <summary>
        /// Invokes the service operation.
        /// </summary>
        /// <param name="credentials">The client network credentials.</param>
        /// <param name="methodBase">The current method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        protected object InvokeChannel(NetworkCredential credentials, MethodBase methodBase, params object[] parameters)
        {
            return InvokeChannel<object>(credentials, methodBase, parameters);
        }

        /// <summary>
        /// Invokes the service operation.
        /// </summary>
        /// <typeparam name="T">The type of the result</typeparam>
        /// <param name="credentials">The client network credentials.</param>
        /// <param name="methodBase">The current method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        protected T InvokeChannel<T>(NetworkCredential credentials, MethodBase methodBase, params object[] parameters)
        {
            return InvokeChannelInternal<T>(credentials, methodBase, parameters);
        }

        void InvokeChannelInternal(NetworkCredential credentials, MethodBase methodBase, params object[] parameters)
        {
            InvokeChannelInternal<object>(credentials, methodBase, throwInlineFaultExceptions, parameters);
        }

        T InvokeChannelInternal<T>(NetworkCredential credentials, MethodBase methodBase, params object[] parameters)
        {
            return InvokeChannelStatic<T>(this, credentials, methodBase, throwInlineFaultExceptions, parameters);
        }

        static T InvokeChannelStatic<T>(ClientChannel clientChannel, NetworkCredential credentials, MethodBase methodBase, bool throwInlineFaultExceptions, params object[] parameters)
        {
            bool asyncPattern = methodBase.Name.StartsWith("Begin");

            return InvokeChannelStatic<T>(clientChannel, credentials, GetContractMethod(clientChannel.ContractType, methodBase, asyncPattern, false), throwInlineFaultExceptions, parameters);
        }

        static T InvokeChannelStatic<T>(ClientChannel clientChannel, NetworkCredential credentials, ContractMethod method, bool throwInlineFaultExceptions, params object[] parameters)
        {
            bool hasException = false;
            T result = default(T);

            if (method.AsyncPattern)
            {
                IAsyncResult r = InvokeChannelStaticAsAsync(clientChannel, credentials, method, throwInlineFaultExceptions, parameters);

                if (r != null)
                {
                    return (T)r;
                }

                return result;
            }

            ChannelContextMode mode = ChannelContextMode.Dedicated;

            object channel = null;
            object[] correlation = null;
            Exception exception = null;
            ChannelInvokeContext invokeContext = null;
            try
            {
                object obj;
                if (clientChannel.HandleBeforeInvocation(method, method.Method, parameters, out obj, out correlation, out invokeContext))
                {
                    channel = clientChannel.GetChannel(credentials, method, out mode);

                    obj = method.Method.Invoke(channel, parameters);

                    clientChannel.HandleChannelAfterCall(channel, method, false, ClientChannel.EndChannelUsageUsingAbort, mode);
                }

                if (obj != null)
                {
                    result = (T)obj;
                }
            }
            catch (TargetInvocationException ex)
            {
                if (channel != null)
                {
                    clientChannel.HandleChannelAfterCall(channel, method, true, ClientChannel.EndChannelUsageUsingAbort, mode);
                }

                exception = ex;
                hasException = true;
            }

            if (!hasException)
            {
                clientChannel.HandleAfterInvocation(invokeContext, method, method.Method, parameters, result, correlation);
            }
            else
            {
                clientChannel.HandleInvokeException(exception, method, throwInlineFaultExceptions, parameters);
            }

            return result;
        }
        #endregion

        #region Async Invocations
        #region AsyncInvocationParams
        class AsyncInvocationParams
        {
            private bool useAsyncCallback;
            public bool UseAsyncCallback
            {
                get { return useAsyncCallback; }
                set { useAsyncCallback = value; }
            }

            private bool throwInlineFaultExceptions;
            public bool ThrowInlineFaultExceptions
            {
                get { return throwInlineFaultExceptions; }
                set { throwInlineFaultExceptions = value; }
            }

            private ContractMethod method;
            public ContractMethod Method
            {
                get { return method; }
                set { method = value; }
            }

            private AsyncCallback callback;
            public AsyncCallback Callback
            {
                get { return callback; }
                set { callback = value; }
            }

            private object channel;
            public object Channel
            {
                get { return channel; }
                set { channel = value; }
            }

            private object asyncState;
            public object AsyncState
            {
                get { return asyncState; }
                set { asyncState = value; }
            }

            private ClientChannel clientChannel;
            public ClientChannel ClientChannel
            {
                get { return clientChannel; }
                set { clientChannel = value; }
            }

            public ChannelInvokeContext InvokeContext { get; set; }
            public ChannelContextMode Mode { get; set; }

            public AsyncInvocationParams(ChannelInvokeContext invokeContext, ClientChannel clientChannel, object channel, ContractMethod method, bool throwInlineFaultExceptions, AsyncCallback callback, object asyncState, ChannelContextMode mode)
            {
                this.throwInlineFaultExceptions = throwInlineFaultExceptions;
                this.clientChannel = clientChannel;
                this.channel = channel;
                this.method = method;
                this.asyncState = asyncState;
                this.callback = callback;
                this.useAsyncCallback = true;
                Mode = mode;
                InvokeContext = invokeContext;
            }
        }
        #endregion

        /// <summary>
        /// Invokes the service asynchrnous operation.
        /// </summary>
        /// <param name="baseMethodName">The name of the method base style. (GetFoo instead of Begin/EndGetFoo).</param>
        /// <param name="callback">The asynchronous invocation callback.</param>
        /// <param name="asyncState">The user-defined async state object.</param>
        /// <param name="parameters">The parameters.</param>
        protected IAsyncResult InvokeAsync(string baseMethodName, AsyncCallback callback, object asyncState, params object[] parameters)
        {
            return InvokeAsync(null, baseMethodName, callback, asyncState, parameters);
        }

        /// <summary>
        /// Invokes the service asynchrnous operation.
        /// </summary>
        /// <param name="credentials">The client network credentials.</param>
        /// <param name="baseMethodName">Name of the base method.</param>
        /// <param name="callback">The asynchronous invocation callback.</param>
        /// <param name="asyncState">The user-defined async state object.</param>
        /// <param name="parameters">The parameters.</param>
        IAsyncResult InvokeAsync(NetworkCredential credentials, string baseMethodName, AsyncCallback callback, object asyncState, params object[] parameters)
        {
            ContractMethod method = GetRuntimeDescription(ContractType).ContractMethods["Begin" + baseMethodName];

            return InvokeChannelAsyncStatic(this, credentials, method, ThrowInlineFaultExceptions, callback, asyncState, parameters);
        }

        /// <summary>
        /// Invokes the service asynchrnous operation.
        /// </summary>
        /// <param name="methodBase">The current method.</param>
        /// <param name="callback">The callback.</param>
        /// <param name="asyncState">The user-defined async state object.</param>
        /// <param name="parameters">The parameters.</param>
        protected IAsyncResult InvokeChannelAsync(MethodBase methodBase, AsyncCallback callback, object asyncState, params object[] parameters)
        {
            return InvokeChannelAsync(null, methodBase, callback, asyncState, parameters);
        }

        /// <summary>
        /// Invokes the service asynchrnous operation.
        /// </summary>
        /// <param name="credentials">The client network credentials.</param>
        /// <param name="methodBase">The current method.</param>
        /// <param name="callback">The asynchronous invocation callback.</param>
        /// <param name="asyncState">The user-defined async state object.</param>
        /// <param name="parameters">The parameters.</param>
        protected IAsyncResult InvokeChannelAsync(NetworkCredential credentials, MethodBase methodBase, AsyncCallback callback, object asyncState, params object[] parameters)
        {
            return InvokeChannelAsyncStatic(this, credentials, GetContractMethod(ContractType, methodBase, true, true), ThrowInlineFaultExceptions, callback, asyncState, parameters);
        }

        static IAsyncResult InvokeChannelAsyncStatic(ClientChannel clientChannel, NetworkCredential credentials, ContractMethod method, bool throwInlineFaultExceptions, AsyncCallback callback, object asyncState, params object[] parameters)
        {
            ChannelContextMode mode = ChannelContextMode.Dedicated;
            bool exceptionOccured = false;
            AsyncInvocationParams param = null;

            Array.Resize(ref parameters, parameters.Length + 2);

            parameters[parameters.Length - 1] = param;
            parameters[parameters.Length - 2] = new AsyncCallback(OnAsyncInvokeCompleted);

            Exception exception = null;
            object channel = null;
            object result = null;
            object[] correlation = null;
            ChannelInvokeContext invokeContext = null;
            try
            {
                IAsyncResult asyncResult = null;

                if (clientChannel.HandleBeforeInvocation(method, method.Method, parameters, out result, out correlation, out invokeContext))
                {
                    channel = clientChannel.GetChannel(credentials, method, out mode);

                    parameters[parameters.Length - 1] = param = new AsyncInvocationParams(invokeContext, clientChannel, channel,
                        method, throwInlineFaultExceptions, callback, asyncState, mode);

                    asyncResult = method.Method.Invoke(channel, parameters) as IAsyncResult;
                    result = asyncResult;
                }
                else if (callback != null)
                {
                    ChannelInvokeAsyncResult resultContext = result as ChannelInvokeAsyncResult
                        ?? new ChannelInvokeAsyncResult(result, asyncState);

                    callback.BeginInvoke(resultContext, null, null);
                }

                return asyncResult;
            }
            catch (Exception ex)
            {
                if (channel != null)
                {
                    clientChannel.HandleChannelAfterCall(channel, method, true, ClientChannel.EndChannelUsageUsingAbort, mode);
                }

                exception = ex;
                exceptionOccured = true;
            }

            if (exceptionOccured)
            {
                clientChannel.HandleInvokeException(param, AsyncInvokeStage.Begin,
                    exception, method, throwInlineFaultExceptions, parameters);

                if (callback != null)
                {
                    callback.Invoke(new ChannelInvokeAsyncResult(result, asyncState));
                }
            }
            else
            {
                clientChannel.HandleAfterInvocation(invokeContext, method, method.Method, parameters, result, correlation);
            }

            return null;
        }

        static IAsyncResult InvokeChannelStaticAsAsync(ClientChannel clientChannel, NetworkCredential credentials, ContractMethod method, bool throwInlineFaultExceptions, params object[] parameters)
        {
            ChannelContextMode mode = ChannelContextMode.Dedicated;
            bool exceptionOccured = false;
            AsyncInvocationParams param = null;

            object asyncState = parameters[parameters.Length - 1];
            AsyncCallback callback = parameters[parameters.Length - 2] as AsyncCallback;

            Exception exception;
            object channel = null;
            object result = null;
            object[] correlation = null;
            ChannelInvokeContext invokeContext = null;
            try
            {
                IAsyncResult asyncResult = null;

                if (clientChannel.HandleBeforeInvocation(method, method.Method, parameters, out result, out correlation, out invokeContext))
                {
                    channel = clientChannel.GetChannel(credentials, method, out mode);

                    param = new AsyncInvocationParams(invokeContext, clientChannel, channel,
                        method, throwInlineFaultExceptions, callback, asyncState, mode);
                    parameters[parameters.Length - 1] = param;
                    parameters[parameters.Length - 2] = new AsyncCallback(OnAsyncInvokeCompleted);

                    asyncResult = method.Method.Invoke(channel, parameters) as IAsyncResult;
                    result = asyncResult;
                }
                else if (callback != null)
                {
                    asyncResult = result as IAsyncResult;
                    Action a = () => callback(asyncResult);
                    a.BeginInvoke(null, null);
                }

                return asyncResult;
            }
            catch (Exception ex)
            {
                if (channel != null)
                {
                    clientChannel.HandleChannelAfterCall(channel, method, true, ClientChannel.EndChannelUsageUsingAbort, mode);
                }

                exception = ex;
                exceptionOccured = true;
            }

            if (exceptionOccured)
            {
                clientChannel.HandleInvokeException(param, AsyncInvokeStage.Begin,
                    exception, method, throwInlineFaultExceptions, parameters);

                if (callback != null)
                {
                    callback.Invoke(new ChannelInvokeAsyncResult(result, asyncState));
                }
            }
            else
            {
                clientChannel.HandleAfterInvocation(invokeContext, method, method.Method, parameters, result, correlation);
            }

            return null;
        }

        static void OnAsyncInvokeCompleted(IAsyncResult result)
        {
            bool hasException = false;
            AsyncInvocationParams param = result.AsyncState as AsyncInvocationParams;

            bool activeCallback = (param.Callback != null);

            MethodBase endMethod = param.Method.EndMethod;
            object[] parameters = null;

            object invokeResult = null;
            ChannelInvokeContext invokeContext = param.InvokeContext;
            object[] correlation = null;
            Exception exception = null;
            try
            {
                parameters = new object[] { result };

                if (param.ClientChannel.HandleBeforeInvocation(param.InvokeContext, param.Method, endMethod, parameters, out invokeResult, out correlation))
                {
                    invokeResult = endMethod.Invoke(param.Channel, parameters);
                }

                param.ClientChannel.HandleChannelAfterCall(param.Channel, param.Method, false, ClientChannel.EndChannelUsageUsingAbort, param.Mode);
            }
            catch (Exception ex)
            {
                param.ClientChannel.HandleChannelAfterCall(param.Channel, param.Method, true, ClientChannel.EndChannelUsageUsingAbort, param.Mode);

                exception = ex;
                hasException = true;
            }

            if (hasException)
            {
                activeCallback = (param.ClientChannel.HandleInvokeException(param, AsyncInvokeStage.End,
                    exception, param.Method, endMethod, param.ThrowInlineFaultExceptions, parameters) == InvokeErrorAction.Suppress);
            }
            else
            {
                param.ClientChannel.HandleAfterInvocation(invokeContext, param.Method, endMethod, parameters, invokeResult, correlation);
            }

            if (activeCallback)
            {
                param.Callback.Invoke(new ChannelInvokeAsyncResult(invokeResult, param.AsyncState));
            }
        }
        #endregion

        #region Global Exception Handling
        internal static Exception GetProperException(Exception exception, bool throwInlineFaultExceptions)
        {
            if (exception is TargetInvocationException)
            {
                exception = exception.InnerException;
            }

            if (throwInlineFaultExceptions)
            {
                //silent catch, if can't it will throw the original error
                try
                {
                    Type exceptionType = exception.GetType();

                    if (exceptionType.IsGenericType && exceptionType.GetGenericTypeDefinition() == typeof(FaultException<>))
                    {
                        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;

                        PropertyInfo pi = exceptionType.GetProperty("Detail", flags);

                        if (pi != null)
                        {
                            Exception inlineException = (Exception)pi.GetValue(exception, flags, null, null, null) as Exception;

                            if (inlineException != null)
                            {
                                exception = inlineException;
                            }
                        }
                    }
                }
                catch { }
            }

            return exception;
        }

        /// <summary>
        /// Occurs when invocation exceptions take place in all client channels.
        /// </summary>
        public static event EventHandler<ChannelExceptionEventArgs> GlobalInvokeException;
        internal static void OnGlobalInvokeException(object sender, ChannelExceptionEventArgs e)
        {
            var evt = GlobalInvokeException;
            if (evt != null)
            {
                evt(sender, e);
            }
        }
        #endregion

        #region IExtensibleObject<ClientChannel> Members
        ExtensionCollection<ClientChannel> extensions;
        /// <summary>
        /// Gets a collection of extension objects for this extensible object.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.ServiceModel.IExtensionCollection`1"/> of extension objects.</returns>
        public IExtensionCollection<ClientChannel> Extensions
        {
            get { return extensions; }
        }
        #endregion
    }
}